//
// Created by 吴喆 on 2020/3/3.
//

#include "GameObject.h"
#include "Playable.h"
#include "Camera.h"
#include "../util/ConfigUtil.h"

Camera::Camera(float &maxSpeed, glm::vec3 position, glm::vec3 up, float yaw, float pitch) : maxSpeed(maxSpeed),
                                                                                            Front(glm::vec3(0.0f, 0.0f,
                                                                                                            -1.0f)),
                                                                                            MovementSpeed(SPEED),
                                                                                            MouseSensitivity(
                                                                                                    SENSITIVITY),
                                                                                            Zoom(ZOOM) {
//        Input::getInstance()->attach2CursorMove(this);
    Input::getInstance()->attach2KeyboardClick(this);
    position = position;
    WorldUp = up;
    Yaw = yaw;
    Pitch = pitch;
    updateCameraVectors();
}

void Camera::onKeyboardInput(KeyEvent *event) {
//    std::cout << position.z << std::endl;
    if (event->key == GLFW_KEY_S) {
        processKeyboard(CameraMovement::FORWARD, 1);
    }
    if (event->key == GLFW_KEY_W) {
        processKeyboard(CameraMovement::BACKWARD, 1);
    }
}

void Camera::processKeyboard(CameraMovement direction, float deltaTime) {
    float velocity = MovementSpeed * deltaTime;
    if (direction == CameraMovement::FORWARD) {
        dist += velocity;
        position += Front * velocity;
    }
    if (direction == CameraMovement::BACKWARD) {
        dist -= velocity;
        position -= Front * velocity;
    }
    if (direction == CameraMovement::LEFT)
        position -= Right * velocity;
    if (direction == CameraMovement::RIGHT)
        position += Right * velocity;
    if (direction == CameraMovement::VIEW_UP) {
        Pitch += velocity * 2;
        updateCameraVectors();
    }
    if (direction == CameraMovement::VIEW_DOWN) {
        Pitch -= velocity * 2;
        updateCameraVectors();
    }
    if (direction == CameraMovement::VIEW_LEFT) {
        Yaw -= velocity * 2;
        updateCameraVectors();
    }
    if (direction == CameraMovement::VIEW_RIGHT) {
        Yaw += velocity * 2;
        updateCameraVectors();
    }
    if (direction == CameraMovement::SPACE) {
        position += Up * velocity;
        updateCameraVectors();
    }
}

void Camera::processMouseMovement(float xoffset, float yoffset, bool constrainPitch) {
    xoffset *= MouseSensitivity;
    yoffset *= MouseSensitivity;

    Yaw += xoffset;
    Pitch += yoffset;

    // Make sure that when pitch is out of bounds, screen doesn't get flipped
    if (constrainPitch) {
        if (Pitch > 89.0f)
            Pitch = 89.0f;
        if (Pitch < -89.0f)
            Pitch = -89.0f;
    }

    // Update Front, Right and Up Vectors using the updated Euler angles
    updateCameraVectors();
}

void Camera::updateCameraVectors() {
    // Calculate the new Front vector
    glm::vec3 front;
    front.x = cos(glm::radians(Yaw)) * cos(glm::radians(Pitch));
    front.y = sin(glm::radians(Pitch));
    front.z = sin(glm::radians(Yaw)) * cos(glm::radians(Pitch));
    Front = glm::normalize(front);
    // Also re-calculate the Right and Up vector
    Right = glm::normalize(glm::cross(Front,
                                      WorldUp));  // Normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.
    Up = glm::normalize(glm::cross(Right, Front));
}

void Camera::initTrackObject(GameObject *gameObject) {
    trackObject = gameObject;
    currentCenter = glm::vec3(trackObject->currCenterPosition, 0);
}

glm::mat4 Camera::getViewMatrix() {
    return glm::lookAt(glm::vec3(currentCenter.x, currentCenter.y, dist),
                       currentCenter, Up);
}

glm::mat4 Camera::getOthoProjectionMatrix() {
    glm::mat4 projection = glm::ortho(
            (currentCenter * std::stod(ConfigUtil::getProp(ConfigConst::SCREEN_HEIGHT)) / 10).x -
            std::stod(ConfigUtil::getProp(ConfigConst::SCREEN_WIDTH)) / 2,
            (currentCenter * std::stod(ConfigUtil::getProp(ConfigConst::SCREEN_HEIGHT)) / 10).x +
            std::stod(ConfigUtil::getProp(ConfigConst::SCREEN_WIDTH)) / 2,
            (currentCenter * std::stod(ConfigUtil::getProp(ConfigConst::SCREEN_HEIGHT)) / 10).y -
            std::stod(ConfigUtil::getProp(ConfigConst::SCREEN_HEIGHT)) / 2,
            (currentCenter * std::stod(ConfigUtil::getProp(ConfigConst::SCREEN_HEIGHT)) / 10).y +
            std::stod(ConfigUtil::getProp(ConfigConst::SCREEN_HEIGHT)) / 2);
    return projection;
}

glm::mat4 Camera::getPerspectiveViewMatrix() {
    glm::mat4 projection = glm::perspective(glm::radians(Zoom),
                                            std::stof(ConfigUtil::getProp(ConfigConst::SCREEN_WIDTH)) /
                                            std::stof(ConfigUtil::getProp(ConfigConst::SCREEN_HEIGHT)), 0.1f,
                                            100.0f);
    return projection;
}

void Camera::ProcessMouseScroll(float yoffset) {
    if (Zoom >= 1.0f && Zoom <= 45.0f)
        Zoom -= yoffset;
    if (Zoom <= 1.0f)
        Zoom = 1.0f;
    if (Zoom >= 45.0f)
        Zoom = 45.0f;
}

void Camera::update() {

    if (trackObject->direction == DIRECTION::RIGHT) {
        distCenter = glm::vec3(trackObject->currCenterPosition.x + 3, trackObject->currCenterPosition.y, 0);
    }
    if (trackObject->direction == DIRECTION::LEFT) {
        distCenter = glm::vec3(trackObject->currCenterPosition.x - 3, trackObject->currCenterPosition.y, 0);
    }
    float centerDist = glm::distance(distCenter, currentCenter);
    if (abs(centerDist) > 0.5) {
        currentSpeed = distSpeedMapFunction(centerDist);
        glm::vec3 v = glm::normalize(distCenter - currentCenter);
//        std::cout << centerDist << ":" << v.x << "," << v.y << std::endl;
        currentCenter += v * currentSpeed;
    }
}

float Camera::distSpeedMapFunction(float centerDist) {
    assert(centerDist >= 0);
    return pow((-1.0f / (centerDist + 1.0f) + 1.0f), 6) * maxSpeed / 10;
}

